package centerberg.game;

import centerberg.engine.Camera;
import centerberg.engine.Drawable;
import centerberg.engine.Engine;
import centerberg.engine.RigidBody;
import centerberg.engine.PhysicsObject;
import centerberg.engine.Texture;
import centerberg.engine.Vector;

import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import java.util.Map;

/**
 * The main class representing the avatar the player controls.
 * Has several forms it can take on with different physical
 * abilities.
 * 
 * @author Andreas Svanberg
 */
public class Player extends RigidBody implements Camera, Drawable
{
    /**
     * The different forms the avatar can take on.
     */
    private enum Form
    {
        NORMAL(51, Engine.getInstance().resourceManager.textures.getTexture("player_normal")),
        FAT(100, Engine.getInstance().resourceManager.textures.getTexture("player_fat"));
        
        private final double mass;
        private final Texture texture;
        
        Form(double mass, Texture texture)
        {
            this.mass = mass;
            this.texture = texture;
        }
        
        /**
         * Returns the next form.
         * 
         * @return the next form
         */
        public Form next()
        {
            return values()[(ordinal() + 1) % values().length];
        }
    }
    
    private static final int MAX_JUMPS = 2;
    private static final int MAX_SPEED = 5;
    private static final double MIN_Y_VELOCITY = -1f / 5;
    private static final double MOVE_SPEED = 1f / 10;
    private static final int TRANSFORM_COOLDOWN = 5 * 1000;
    private static final Vector JUMP_FORCE = new Vector(0, -20);
    
    private Form form;
    private int jumpCount;
    private int transformCooldown;
    private boolean jumpStillDown;
    
    /**
     * Creates a new playr with the given shape starting in the given form.
     * 
     * @param shape shape of the player
     * @param form  initial form of the player
     */
    private Player(Shape shape, Form form)
    {
        super(shape);
        this.form =  form;
        this.jumpCount = MAX_JUMPS;
        
        setBounciness(0);
        setMass(form.mass);
        
        // make this the camera so it is always focused on the player
        Engine.getInstance().render.setActiveCamera(this);
    }
    
    /**
     * Creates a new player with the given attributes.
     * 
     * @param attributes the attributes specifying the data
     *                   determining how to create the player
     * @return the created player object
     */
    public static Player create(Map<String, String> attributes)
    {
        try
        {
            double x = Double.parseDouble(attributes.get("x"));
            double y = Double.parseDouble(attributes.get("y"));
            
            for (Form form : Form.values())
                form.texture.setPosition(x, y);
            
            Form form = Form.NORMAL;
            Shape s = new Rectangle2D.Double(x, y, 20, 60);
            
            return new Player(s, form);
        }
        catch (NumberFormatException e)
        {
            throw new IllegalArgumentException("Missing or not numerical attribute");
        }
        catch (NullPointerException e)
        {
            throw new IllegalArgumentException("Required attribute missing");
        }
    }
    
    /**
     * Internal function determining if we can jump.
     */
    private boolean canJump()
    {
        return !jumpStillDown && jumpCount > 0;
    }
    
    /**
     * {@inheritDoc}. If we collide with {@link Ground} we
     * can jump again.
     * 
     * @param other {@inheritDoc}
     */
    public void collided(PhysicsObject other)
    {
        if (other instanceof Ground)
            jumpCount = MAX_JUMPS;
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public double getMass()
    {
        return form.mass;
    }
    
    /**
     * {@inheritDoc}
     */
    public Texture getTexture()
    {
        return form.texture;
    }
    
    /**
     * {@inheritDoc}
     */
    public Rectangle getViewport()
    {
        Vector center = getCenter();
        Rectangle viewport = new Rectangle((int)center.getX() - 400, (int)center.getY() - 350, 800, 600);
        return viewport;
    }
    
    /**
     * Attempts to jump. If we are allowed and not moving
     * too fast we will be allowed to jump. If it is the 
     * second jump we do not jump as high as the first.
     */
    private void jump()
    {
        if (!canJump())
            return;

        if (getVelocity().getY() < MIN_Y_VELOCITY)
            return;
        
        jumpCount--;
        jumpStillDown = true;
        
        if (jumpCount == 0)
            addForce(JUMP_FORCE.multiply(0.3));
        else
            addForce(JUMP_FORCE);
        
        Engine.getInstance().sound.playSound("jump", 0, 0, 0);
    }
    
    /**
     * Updates the player object. Reducing the transform-
     * cooldown as well as checking if any movement keys
     * are pressed and if so, updates velocities. Finally
     * we reduce the x-velocity to simulate friction.
     * 
     * @param elapsed {@inheritDoc}
     */
    public void tick(double elapsed)
    {
        if (transformCooldown > 0)
            transformCooldown -= elapsed;
        
        if (Keys.jump.isDown())
            jump();
        else
            jumpStillDown = false;
        
        if (Keys.right.isDown() && getVelocity().getX() < MAX_SPEED)
            addVelocity(new Vector(MOVE_SPEED, 0));
        
        if (Keys.left.isDown() && -getVelocity().getX() < MAX_SPEED)
            addVelocity(new Vector(-MOVE_SPEED, 0));
        
        if (Keys.transform.isDown() && transformCooldown <= 0)
            transform();
        
        getVelocity().set(0, getVelocity().getX() * 0.7);
    }
    
    /**
     * Transforms the player to the next form depending on
     * the current form.
     */
    private void transform()
    {
        transformCooldown = TRANSFORM_COOLDOWN;
        form = form.next();
        setMass(form.mass);
        
        Engine.getInstance().sound.playSound("transform", 0, 0, 0);
    }
    
    /**
     * {@inheritDoc}
     */
    public void translated(double dx, double dy)
    {
        for (Form form : Player.Form.values())
            form.texture.translate(dx, dy);
    }
}